#include <unistd.h>

#include <fstream>

#include "utils.h"

#define BUFF_SIZE 1000

void init_client_queue(std::queue<Client*>& c_queue, const char* data_file,
                       std::vector<Client*>& clients) {
    /*
    Load client info from data_file to c_queue

    Parameter:
    c_queue: queue for storing client data
    data_file: file storing client info
    */
    std::ifstream rfile;
    char buff[BUFF_SIZE];

    rfile.open(data_file);
    Client* tmp;
    printf("client id\tenter time\tserve time\t\n");
    while (rfile.getline(buff, BUFF_SIZE)) {
        int id, enter_time, server_time;
        sscanf(buff, "%d %d %d", &id, &enter_time, &server_time);

        printf("%d\t\t%d\t\t%d\t\t\n", id, enter_time, server_time);

        tmp = new Client(enter_time, server_time, id);
        c_queue.push(tmp);
        clients.push_back(tmp);
    }

    rfile.close();
}

void init_worker(pthread_t* workers, const int num_worker,
                std::queue<Client*>& waiting_client, 
                std::queue<Client*>& clients,
                sem_t* sem, pthread_mutex_t* mutex) {
    /*
    Start threads for rerpesenting worker

    Parameters:

    */
    std::vector<pthread_t> thread_pool;

    WorkerInfo* worker_info;
    for (int i = 0; i < num_worker; i++) {
        worker_info = new WorkerInfo();
        worker_info->mutex = mutex;
        worker_info->sem = sem;
        worker_info->waiting_queue = &waiting_client;
        worker_info->worker_id = i;
        worker_info->clients = &clients;

        pthread_create(&workers[i], NULL, worker, (void*)worker_info);
    }
}

void* worker(void* data) {
    /*
    Define worker behavior

    Parameter:
    data: WorerInfo*
    */
    WorkerInfo* worker_info = (WorkerInfo*) data;
    Client* tmp = NULL;
    auto now = std::chrono::system_clock::now();
    
    while (true) {
        int sem_value;
        sem_getvalue(worker_info->sem, &sem_value);
        if (worker_info->clients->empty() && sem_value == 0) break;

        sem_wait(worker_info->sem);
        pthread_mutex_lock(worker_info->mutex);
        tmp = worker_info->waiting_queue->front();
        worker_info->waiting_queue->pop();
        pthread_mutex_unlock(worker_info->mutex);

        tmp->set_serve_worker_id(worker_info->worker_id);

        // record serve start time
        now = std::chrono::system_clock::now();
        tmp->set_serve_start_time(std::chrono::system_clock::to_time_t(now));

#ifdef UTILS_DEBUG
        tmp->debug_serve_info(worker_info->worker_id);
#endif

        tmp->be_served();

        // record leave time
        now = std::chrono::system_clock::now();
        tmp->set_leave_time(std::chrono::system_clock::to_time_t(now));

#ifdef UTILS_DEBUG
        tmp->debug_leave_info();
#endif
    }
}

void* queueing_client(void* data) {
    /*
    Queueing the clients in queue for entering bank and getting bank number

    Parameters
    --------------------------------
    queue: Need to cast to std::queue<Client>*,
            Storing the queueing clients
    */
    ClientQueueData* queue_data = (ClientQueueData*)data;

    // Unpack args
    std::queue<Client*>* c_queue = queue_data->client_queue;
    std::queue<Client*>* waiting_queue = queue_data->waiting_queue;
    pthread_mutex_t* mutex = queue_data->mutex;
    sem_t* sem = queue_data->sem;

    std::vector<pthread_t> thread_pool;
    int queue_num = 0;

    // multi thread related
    pthread_t thread;

    // record start time for calculating serve time
    auto now = std::chrono::system_clock::now();
    *(queue_data->start_time) = std::chrono::system_clock::to_time_t(now);
    
    int current_time = 0;
    QueueInfo* queue_info;
    Client* client;
    while (!c_queue->empty()) {
        queue_info = new QueueInfo;
        client = c_queue->front();
        client->set_tot_start_time(*(queue_data->start_time));

        // Loop and activate the client when time is up
        int enter_time = client->get_enter_time();
        sleep(enter_time-current_time);        
        current_time = enter_time;

        printf("%d\t\t%d\t\t%s\t\t%s\t\t%s\t\t\n", client->get_id(), enter_time, "--", "--", "Enter Bank");

        // the client now enter the bank, should distribute a queue number for it
        queue_info->client = client;
        queue_info->mutex = mutex;
        queue_info->sem = sem;
        queue_info->queue_num = &queue_num;
        queue_info->waiting_queue = waiting_queue;

        pthread_create(&thread, NULL, get_queue_num, (void*)queue_info);
        thread_pool.push_back(thread);

        c_queue->pop();
    }

    /*
    for (auto beg = thread_pool.begin(); beg != thread_pool.end(); beg++)
        pthread_join(*beg, NULL);
    */
}

void* get_queue_num(void* data) {
    /*
    Client get queue num
    */
    QueueInfo* queue_info = (QueueInfo*) data;

    pthread_mutex_lock(queue_info->mutex);
    sem_post(queue_info->sem);
    // get queue number
    queue_info->client->set_queue_num((*(queue_info->queue_num))++);
    queue_info->client->print_queue_info();

    // push to waiting queue
    queue_info->waiting_queue->push(queue_info->client);
    pthread_mutex_unlock(queue_info->mutex);

    delete queue_info;
    pthread_exit(0);
}
